package com.hrbxlh.virtualization.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.hrbxlh.virtualization.dao.*;
import com.hrbxlh.virtualization.dto.ImageDTO;
import com.hrbxlh.virtualization.enums.ContainerRoleEnum;
import com.hrbxlh.virtualization.enums.ImageStatusEnum;
import com.hrbxlh.virtualization.exception.GlobalException;
import com.hrbxlh.virtualization.param.ImageCoverParam;
import com.hrbxlh.virtualization.param.ImageCreateParam;
import com.hrbxlh.virtualization.pojo.*;
import com.hrbxlh.virtualization.result.ResultEnum;
import com.hrbxlh.virtualization.service.*;
import com.hrbxlh.virtualization.vo.DesktopVO;
import com.hrbxlh.virtualization.vo.ImageVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * @author cheer
 */
@Service
public class ImageServiceImpl implements ImageService {

    private Logger logger = LoggerFactory.getLogger(ImageServiceImpl.class);

    @Autowired
    private ContainerService containerService;

    @Autowired
    private ConfigService configService;

    @Autowired
    private ImageMapper imageMapper;

    @Autowired
    private ImageMapperExt imageMapperExt;

    @Autowired
    private ImageAllocationMapper imageAllocationMapper;

    @Autowired
    private ImageAllocationService imageAllocationService;

    @Autowired
    private ContainerMapper containerMapper;

    @Autowired
    private ClusterMapper clusterMapper;

    @Autowired
    private ClusterService clusterService;

    @Autowired
    private DockerService dockerService;

    @Override
    public boolean isImageExists(Long imageId) {
        return imageMapperExt.selectIdByPrimaryKeyLimit1(imageId) != null;
    }

    @Override
    public Image queryImageById(Long imageId) {
        return imageMapper.selectByPrimaryKey(imageId);
    }

    @Override
    public List<ImageVO> listImages(Boolean isInitial) {
        List<Image> imageList = listSharedImages(isInitial);

        List<ImageVO> imageVOList = new ArrayList<>();
        for (Image image : imageList) {
            ImageVO imageVO = new ImageVO().convertFor(image);
            imageVOList.add(imageVO);
        }
        return imageVOList;
    }

    @Override
    @SuppressWarnings("unchecked")
    public PageInfo<ImageVO> listImagesByPage(Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<ImageDTO> imageDTOList = imageMapperExt.listSharedImageDTOs();
        PageInfo pageInfo = new PageInfo(imageDTOList);

        List<ImageVO> imageVOList = new ArrayList<>();
        for (ImageDTO imageDTO : imageDTOList) {
            ImageVO imageVO = imageDTO.convertToImageVO();
            setStatus(imageVO);
            imageVOList.add(imageVO);
        }
        pageInfo.setList(imageVOList);
        return pageInfo;
    }

    @Override
    @Transactional
    public void deleteImage(Long imageId, Boolean isForced) {
        Image image = imageMapper.selectByPrimaryKey(imageId);
        if (image == null) {
            throw new GlobalException(ResultEnum.IMAGE_NOT_EXISTS);
        }
        if (image.getInitial()) {
            throw new GlobalException(ResultEnum.IMAGE_DELETE_INITIAL_NOT_ALLOWED);
        }
        boolean isImageUsed = isImageUsed(imageId);
        if (isImageUsed && !isForced) {
            throw new GlobalException(ResultEnum.IMAGE_IS_USED);
        }

        // 删除镜像之前要删除所有使用该镜像的容器
        deleteContainersByImageId(imageId);

        // 标记使用该镜像的集群为broken
        setClusterBroken(imageId);

        // 删除容器环境的镜像
        dockerService.deleteImage(image.getDockerName());

        if (isImageUsed) {
            deleteImageAllocation(imageId);
        }
        imageMapper.deleteByPrimaryKey(imageId);
    }

    @Override
    public List<ImageVO> listImages(Boolean isInitial, String stuorteaId) {
        List<Image> imageList = listSharedAndSelfImages(isInitial, stuorteaId);

        List<ImageVO> imageVOList = new ArrayList<>();
        for (Image image : imageList) {
            ImageVO imageVO = new ImageVO().convertFor(image);
            imageVOList.add(imageVO);
        }
        return imageVOList;
    }

    @Override
    @SuppressWarnings("unchecked")
    public PageInfo<ImageVO> listImagesByPage(Integer pageNum, Integer pageSize, String stuorteaId) {
        PageHelper.startPage(pageNum, pageSize);
        List<Image> imageList = listSelfImages(stuorteaId);
        PageInfo pageInfo = new PageInfo(imageList);

        List<ImageVO> imageVOList = new ArrayList<>();
        for (Image image : imageList) {
            ImageVO imageVO = new ImageVO().convertFor(image);
            setStatus(imageVO);
            imageVOList.add(imageVO);
        }
        pageInfo.setList(imageVOList);
        return pageInfo;
    }

    @Override
    public DesktopVO getDesktop(String stuorteaId, Long imageId) {
        Image image = imageMapper.selectByPrimaryKey(imageId);
        if (image == null) {
            throw new GlobalException(ResultEnum.IMAGE_NOT_EXISTS);
        }

        Container container = containerService.createContainer(image);
        insertContainer(container, stuorteaId);
        String desktopUrl = dockerService.queryDesktopUrl(container.getRancherId());

        DesktopVO desktopVO = new DesktopVO();
        fillContainerToDesktopVO(container, desktopUrl, desktopVO);
        return desktopVO;
    }

    @Override
    public Long createImage(String stuorteaId, ImageCreateParam imageCreateParam) {
        Container container = containerMapper.selectByPrimaryKey(imageCreateParam.getContainerId());
        if (container == null) {
            throw new GlobalException(ResultEnum.CONTAINER_NOT_EXISTS);
        }

        checkImageNameExists(imageCreateParam.getImageName());
        checkImageQuota(stuorteaId);

        String dockerName = UUID.randomUUID().toString();
        dockerService.createImage(container, dockerName);

        Image image = insertImage(imageCreateParam.getImageName(), dockerName, stuorteaId);
        return image.getId();
    }

    private void checkImageNameExists(String imageName) {
        if (imageMapperExt.checkImageNameExists(imageName) != null) {
            throw new GlobalException(ResultEnum.IMAGE_NAME_EXISTS);
        }
    }

    private void checkImageQuota(String stuorteaId) {
        ImageExample imageExample = new ImageExample();
        imageExample.or().andStuorteaIdEqualTo(stuorteaId);
        Integer count = configService.getImageQuota();
        if (imageMapper.countByExample(imageExample) >= count) {
            throw new GlobalException(ResultEnum.IMAGE_QUOTA_EXCEEDED.fillArgs(count));
        }
    }

    @Override
    public Long coverImage(String stuorteaId, ImageCoverParam imageCoverParam) {
        Container container = containerMapper.selectByPrimaryKey(imageCoverParam.getContainerId());
        if (container == null) {
            throw new GlobalException(ResultEnum.CONTAINER_NOT_EXISTS);
        }

        Image image = imageMapper.selectByPrimaryKey(imageCoverParam.getImageId());
        if (image.getShared()) {
            throw new GlobalException(ResultEnum.IMAGE_COVER_SHARED_NOT_ALLOWED);
        }
        if (!Objects.equals(stuorteaId, image.getStuorteaId())) {
            throw new GlobalException(ResultEnum.IMAGE_COVER_NOT_ALLOWED);
        }

        String dockerName = UUID.randomUUID().toString();
        dockerService.createImage(container, dockerName);

        image.setDockerName(dockerName);
        imageMapper.updateByPrimaryKeySelective(image);
        return image.getId();
    }

    @Override
    public DesktopVO repickImage(String stuorteaId, Long containerId, Long imageId) {
        Container container = containerMapper.selectByPrimaryKey(containerId);
        if (container == null) {
            throw new GlobalException(ResultEnum.CONTAINER_NOT_EXISTS);
        }

        containerService.deleteContainer(container);

        return getDesktop(stuorteaId, imageId);
    }

    @Override
    public void shareImage(String stuorteaId, Long imageId) {
        Image image = imageMapper.selectByPrimaryKey(imageId);
        if (image == null) {
            throw new GlobalException(ResultEnum.IMAGE_NOT_EXISTS);
        }
        if (!Objects.equals(stuorteaId, image.getStuorteaId())) {
            throw new GlobalException(ResultEnum.IMAGE_SHARE_NOT_ALLOWED);
        }

        image.setShared(true);
        imageMapper.updateByPrimaryKeySelective(image);
    }

    @Override
    public void unshareImage(String stuorteaId, Long imageId) {
        Image image = imageMapper.selectByPrimaryKey(imageId);
        if (image == null) {
            throw new GlobalException(ResultEnum.IMAGE_NOT_EXISTS);
        }
        if (!Objects.equals(stuorteaId, image.getStuorteaId())) {
            throw new GlobalException(ResultEnum.IMAGE_UNSHARE_NOT_ALLOWED);
        }
        if(!imageAllocationService.canUnshare(imageId)){
            throw new GlobalException(ResultEnum.IMAGE_ALLOCATION_UNSHARE_NOT_ALLOWED);
        }


        image.setShared(false);
        imageMapper.updateByPrimaryKeySelective(image);
    }

    @Override
    @Transactional
    public void deleteImage(Long imageId, Boolean isForced, String stuorteaId) {
        Image image = imageMapper.selectByPrimaryKey(imageId);
        if (image == null) {
            throw new GlobalException(ResultEnum.IMAGE_NOT_EXISTS);
        }
        if (!Objects.equals(image.getStuorteaId(), stuorteaId)) {
            throw new GlobalException(ResultEnum.IMAGE_DELETE_NOT_ALLOWED);
        }

        deleteImage(imageId, isForced);
    }

    private List<Image> listSharedImages(Boolean isInitial) {
        ImageExample imageExample = new ImageExample();
        ImageExample.Criteria criteria = imageExample.createCriteria();
        criteria.andSharedEqualTo(true);
        if (isInitial != null) {
            criteria.andInitialEqualTo(isInitial);
        }
        return imageMapper.selectByExample(imageExample);
    }

    private List<Image> listSharedAndSelfImages(Boolean isInitial, String stuorteaId) {
        ImageExample imageExample = new ImageExample();
        if (isInitial == null) {
            imageExample.or().andInitialEqualTo(true)
                    .andSharedEqualTo(true);
            imageExample.or().andInitialEqualTo(false)
                    .andSharedEqualTo(true);
            imageExample.or().andInitialEqualTo(false)
                    .andSharedEqualTo(false)
                    .andStuorteaIdEqualTo(stuorteaId);
        } else if (isInitial) {
            imageExample.or().andInitialEqualTo(true)
                    .andSharedEqualTo(true);
        } else {
            imageExample.or().andInitialEqualTo(false)
                    .andSharedEqualTo(true);
            imageExample.or().andInitialEqualTo(false)
                    .andSharedEqualTo(false)
                    .andStuorteaIdEqualTo(stuorteaId);
        }
        return imageMapper.selectByExample(imageExample);
    }

    private List<Image> listSelfImages(String stuorteaId) {
        ImageExample imageExample = new ImageExample();
        imageExample.or().andStuorteaIdEqualTo(stuorteaId);
        return imageMapper.selectByExample(imageExample);
    }

    private void setStatus(ImageVO imageVO) {
        ImageStatusEnum imageStatusEnum = isImageUsed(imageVO.getId()) ? ImageStatusEnum.USED : ImageStatusEnum.NOT_USED;
        imageVO.setStatus(imageStatusEnum.getMessage());
        imageVO.setStatusCode(imageStatusEnum.getCode());
    }

    private boolean isImageUsed(Long imageId) {
        if(imageMapperExt.checkImageUsed(imageId) != null){
            return true;
        }

        List<Cluster> clusterList = clusterService.listClustersByImageId(imageId);
        for(Cluster cluster: clusterList){
            if(imageAllocationService.isClusterAllocated(cluster.getId())){
                return true;
            }
        }
        return false;
    }

    private boolean isImageInitial(Long imageId) {
        return imageMapperExt.checkImageInitial(imageId);
    }

    private void deleteContainersByImageId(Long imageId) {
        ContainerExample containerExample = new ContainerExample();
        containerExample.or().andImageIdEqualTo(imageId);
        List<Container> containerList = containerMapper.selectByExample(containerExample);
        containerList.forEach(container -> {
            try {
                containerService.deleteContainer(container);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                throw new GlobalException(ResultEnum.IMAGE_DELETE_ERROR.fillArgs(
                        String.format("删除虚拟机%s失败", container.getRancherId())));
            }
        });
    }

    private void setClusterBroken(Long imageId) {
        ClusterExample clusterExample = new ClusterExample();
        clusterExample.or().andMasterImageIdEqualTo(imageId);
        clusterExample.or().andFirstSlaveImageIdEqualTo(imageId);
        clusterExample.or().andSecondSlaveImageIdEqualTo(imageId);
        Cluster cluster = new Cluster();
        cluster.setBroken(true);
        clusterMapper.updateByExampleSelective(cluster, clusterExample);
    }

    private void deleteImageAllocation(Long imageId) {
        ImageAllocationExample imageAllocationExample = new ImageAllocationExample();
        imageAllocationExample.or().andClusterEqualTo(false)
                .andImageIdEqualTo(imageId);
        imageAllocationMapper.deleteByExample(imageAllocationExample);
    }

    private Image insertImage(String imageName, String dockerName, String stuorteaId) {
        Image image = new Image();
        image.setImageName(imageName);
        image.setDockerName(dockerName);
        image.setInitial(false);
        image.setShared(false);
        image.setStuorteaId(stuorteaId);
        imageMapper.insertSelective(image);
        return image;
    }

    private void insertContainer(Container container, String stuorteaId) {
        container.setStuorteaId(stuorteaId);
        container.setContainerRole(ContainerRoleEnum.SINGLE.getCode());
        containerMapper.insertSelective(container);
    }

    private void fillContainerToDesktopVO(Container container, String desktopUrl, DesktopVO desktopVO) {
        desktopVO.setContainerId(container.getId());
        desktopVO.setDesktopUrl(desktopUrl);
        desktopVO.setImageId(container.getImageId());
    }
}
